import PBF from 'pbf';
import MVT from 'ol/format/MVT';
import { get as getProjection } from 'ol/proj';

class OLMvt {
  static get () {
    function featurePBFReader(tag, feature, pbf) {
      if (tag === 1) {
        feature.id = pbf.readVarint();
      } else if (tag === 2) {
        const end = pbf.readVarint() + pbf.pos;

        while (pbf.pos < end) {
          const key = feature.layer.keys[pbf.readVarint()];
          const value = feature.layer.values[pbf.readVarint()];

          feature.properties[key] = value;
        }
      } else if (tag === 3) {
        feature.type = pbf.readVarint();
      } else if (tag === 4) {
        feature.geometry = pbf.pos;
      }
    }

    function readRawFeature(pbf, layer, i) {
      pbf.pos = layer.features[i];
      const end = pbf.readVarint() + pbf.pos;

      const feature = {
        layer,
        type: 0,
        properties: {},
      }

      pbf.readFields(featurePBFReader, feature, end);

      return feature;
    }
    function layerPBFReader(tag, layer, pbf) {
      if (tag === 15) {
        layer.version = pbf.readVarint();
      } else if (tag === 1) {
        layer.name = pbf.readString();
      } else if (tag === 5) {
        layer.extent = pbf.readVarint();
      } else if (tag === 2) {
        layer.features.push(pbf.pos);
      } else if (tag === 3) {
        layer.keys.push(pbf.readString());
      } else if (tag === 4) {
        let value = null;
        const end = pbf.readVarint() + pbf.pos;

        while (pbf.pos < end) {
          tag = pbf.readVarint() >> 3;
          value = tag === 1
            ? pbf.readString()
            : tag === 2
              ? pbf.readFloat()
              : tag === 3
                ? pbf.readDouble()
                : tag === 4
                  ? pbf.readVarint64()
                  : tag === 5
                    ? pbf.readVarint()
                    : tag === 6
                      ? pbf.readSVarint()
                      : tag === 7
                        ? pbf.readBoolean()
                        : null;
        }
        layer.values.push(value);
      }
    }

    function layersPBFReader(tag, layers, pbf) {
      if (tag === 3) {
        const layer: any = {
          keys: [],
          values: [],
          features: [],
        }
        const end = pbf.readVarint() + pbf.pos;

        pbf.readFields(layerPBFReader, layer, end);
        layer.length = layer.features.length;
        if (layer.length) {
          layers[layer.name] = layer;
        }
      }
    }

    const format: any = new MVT();

    format.readFeatures = function (source, options) {
      const layers = this.layers_;

      options = this.adaptOptions(options);
      const dataProjection: any = getProjection(options.dataProjection);

      dataProjection.setWorldExtent(options.extent);
      options.dataProjection = dataProjection;

      const pbf = new PBF(/** @type {ArrayBuffer} */(source));
      const pbfLayers = pbf.readFields(layersPBFReader, {});
      const features = [];

      for (const name in pbfLayers) {
        if (layers && !layers.includes(name)) {
          continue;
        }
        const pbfLayer = pbfLayers[name];

        const extent = pbfLayer ? [0, 0, pbfLayer.extent, pbfLayer.extent] : null;

        dataProjection.setExtent(extent);

        for (let i = 0, ii = pbfLayer.length; i < ii; ++i) {
          const rawFeature = readRawFeature(pbf, pbfLayer, i);
          const feature = this.createFeature_(pbf, rawFeature, options);

          if (feature !== null) {
            features.push(feature);
          }
        }
      }

      return { features, extent: dataProjection.extent_ }
    }

    return format;
  }
}

export default OLMvt
